package com.smart.admin.service;

import com.google.common.collect.Lists;
import com.smart.admin.model.ConfigHistoryVO;
import com.smart.admin.model.ConfigReceiveInfoVO;
import com.smart.admin.model.ConfigRequest;
import com.smart.config.model.ConfigReceiveInfo;
import com.smart.config.utils.CommonExecuteUtils;
import com.smart.config.utils.GsonTool;
import com.smart.config.constant.RedisConstant;
import com.smart.config.model.ConfigVal;
import com.smart.config.model.ConfigHistory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author xiaoqiang
 * @Version ConfigService.java, v 0.1 2025年03月20日 12:41 xiaoqiang
 * @Description: TODO
 */
@Service
public class ConfigService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigService.class);

    private RedisTemplate<String, String> redisTemplate;

    public ConfigService(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    private static final String LUA_SCRIPT =
            "local version = redis.call('INCR', KEYS[2])\n" +
                    "redis.call('SET', KEYS[1], ARGV[1])\n" +
                    "local configVal = cjson.decode(ARGV[1])\n" +
                    "local historyData = {\n" +
                    "    version = version,\n" +
                    "    createTime = ARGV[3],\n" +
                    "    content = configVal.content,\n" +
                    "    configReceiveInfos = {}\n" +
                    "}\n" +
                    "redis.call('RPUSH', KEYS[3], cjson.encode(historyData))\n" +
                    "local finalMessage = string.gsub(ARGV[2], '{version}', version)\n" +
                    "redis.call('PUBLISH', KEYS[4], finalMessage)\n" +
                    "return version";

    private final RedisScript<Long> saveScript = RedisScript.of(LUA_SCRIPT, Long.class);

    public long saveConfig1(ConfigRequest configRequest) {
        // 构建Keys
        String appName = configRequest.getAppName();
        String group = configRequest.getGroup();
        String dataId = configRequest.getDataId();

        String realKey = RedisConstant.buildRedisKey(appName, group, dataId);
        String versionKey = RedisConstant.buildVersionKey(appName, group, dataId);
        String historyKey = RedisConstant.buildHistoryKey(appName, group, dataId);
        String channelKey = RedisConstant.buildRedisChannelName(appName);

        // 准备参数
        ConfigVal configVal = ConfigVal.builder()
                .appName(appName)
                .group(group)
                .dataId(dataId)
                .content(configRequest.getContent()) // 保持原始类型
                .build();

        String configJson = GsonTool.toJsonString(configVal);
        String pubTemplate = RedisConstant.buildPubMessageFormat(realKey, "{version}"); // 0L为占位符
        String timestamp = Instant.now().toString();

        // 执行脚本
        return redisTemplate.execute(
                saveScript,
                Arrays.asList(realKey, versionKey, historyKey, channelKey),
                configJson, pubTemplate, timestamp
        );

    }



    public Object getConfig(String appName, String group, String dataId) {
        String realKey = RedisConstant.buildRedisKey(appName, group, dataId);
        String configValStr = redisTemplate.opsForValue().get(realKey);
        if (configValStr == null) {
            return null;
        }
        ConfigVal configVal = GsonTool.strToJavaBean(configValStr, ConfigVal.class);
        return configVal.getContent();
    }

    public List<ConfigHistoryVO> getConfigVersions(String appName, String group, String dataId) {

        String historyKey = RedisConstant.buildHistoryKey(appName, group, dataId);

        List<String> historyList = redisTemplate.opsForList().range(historyKey, 0, -1);
        if (CollectionUtils.isEmpty(historyList)) {
            return new ArrayList<>();
        }

        List<ConfigHistoryVO> configHistoryList = new ArrayList<>();
        HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();
        int start = historyList.size() - 1;
        for (int i = start; i >= 0; i--) {
            String history = historyList.get(i);
            ConfigHistory configHistory = GsonTool.fromJson(history, ConfigHistory.class);
            ConfigHistoryVO configHistoryVO = new ConfigHistoryVO();
            BeanUtils.copyProperties(configHistory, configHistoryVO);
            configHistoryList.add(configHistoryVO);
            Long version = configHistoryVO.getVersion();
            String publishMessage = RedisConstant.buildHistoryKeyReceiveKey(
                    appName, group, dataId, version);
            List<String> receiveMessageList = hashOperations.values(publishMessage);
            if (CollectionUtils.isEmpty(receiveMessageList)) {
                continue;
            }
            Map<String, ConfigReceiveInfoVO> receiveInfoMap = buildConfigReceiveInfoMap(receiveMessageList);
            configHistoryVO.setConfigReceiveInfos(receiveInfoMap);
        }


        return configHistoryList;
    }


    private static Map<String, ConfigReceiveInfoVO> buildConfigReceiveInfoMap(List<String> receiveMessageList) {
        List<ConfigReceiveInfo> receiveInfoList =
                receiveMessageList.stream()
                        .map(message -> {
                            ConfigReceiveInfo configReceiveInfo = GsonTool.fromJson(message, ConfigReceiveInfo.class);
                            return configReceiveInfo;
                        })
                        .toList();
        Map<String, ConfigReceiveInfoVO> receiveInfoMap = CommonExecuteUtils.listToMap(receiveInfoList, configReceiveInfo -> {
            return configReceiveInfo.getIp();
        }, configReceiveInfo -> {
            ConfigReceiveInfoVO configReceiveInfoVO = new ConfigReceiveInfoVO();
            BeanUtils.copyProperties(configReceiveInfo, configReceiveInfoVO);
            return configReceiveInfoVO;
        });
        return receiveInfoMap;
    }


    public long saveConfig(ConfigRequest configRequest) {


        String realKey = RedisConstant.buildRedisKey(configRequest.getAppName(), configRequest.getGroup(), configRequest.getDataId());
        //String content = GsonTool.toJsonString(configRequest.getContent());

        ConfigVal configVal = ConfigVal.builder()
                .appName(configRequest.getAppName())
                .group(configRequest.getGroup())
                .dataId(configRequest.getDataId())
                .content(configRequest.getContent())
                .build();
        redisTemplate.opsForValue().set(realKey, GsonTool.toJsonString(configVal));

        Long version = buildHistoryConfig(
                configRequest.getAppName(),
                configRequest.getGroup(),
                configRequest.getDataId(),
                configRequest.getContent()
        );

        String channelName = RedisConstant.buildRedisChannelName(configRequest.getAppName());

        String pubMessage = RedisConstant.buildPubMessage(realKey, version);

        long result = redisTemplate.convertAndSend(channelName, pubMessage);
        LOGGER.info("[ConfigController#save] send message to channel:{},realKey:{},, result:{}"
                , channelName, realKey, result);
        return result;
    }

    private Long buildHistoryConfig(String appName, String group, String dataId, Object content) {
        String historyKey = RedisConstant.buildHistoryKey(appName, group, dataId);

        String versionKey = RedisConstant.buildVersionKey(appName, group, dataId);
        Long version = redisTemplate.opsForValue().increment(versionKey, 1);
        ConfigHistory confingHistory = new ConfigHistory();
        confingHistory.setCreateTime(new Date());
        confingHistory.setVersion(version);
        confingHistory.setContent(content);
        redisTemplate.opsForList().rightPush(historyKey, GsonTool.toJsonString(confingHistory));
        return confingHistory.getVersion();
    }


    /**
     * 删除配置
     *
     * @param appName
     * @param group
     * @param dataId
     */
    public Integer deleteConfig(String appName, String group, String dataId) {


        String realKey = RedisConstant.buildRedisKey(appName, group, dataId);

        Cursor<String> allKeys = redisTemplate.scan(ScanOptions.scanOptions().match(realKey + "*").build());
        AtomicInteger autoDeleteCount = new AtomicInteger(0);
        allKeys.forEachRemaining(key -> {
            redisTemplate.delete(key);
            autoDeleteCount.incrementAndGet();
        });
        return autoDeleteCount.get();

    }

}
